import re
import logging
import time
import threading
from flask import (
    Flask,
    request,
    send_from_directory,
    make_response,
    render_template,
    render_template_string,
    jsonify,
    session
)
import os
import json
import shutil
import subprocess
import paramiko
from datetime import datetime
from tool import save_config_file, get_file_list, handle_upgrade_program

# 配置日志
logging.basicConfig(level=logging.DEBUG, format='%(asctime)s - %(levelname)s - %(message)s')
logging.debug(f"Current log level: {logging.getLevelName(logging.getLogger().level)}")

# 指定自定义的模板文件夹路径
TEMPLATE_FOLDER = "uploads"
# 指定静态文件文件夹路径
STATIC_FOLDER = TEMPLATE_FOLDER + "/page"
app = Flask(__name__, template_folder=TEMPLATE_FOLDER, static_folder=STATIC_FOLDER)

UPLOAD_FOLDER = "uploads"

if not os.path.exists(UPLOAD_FOLDER):
    os.makedirs(UPLOAD_FOLDER)

app.config["UPLOAD_FOLDER"] = UPLOAD_FOLDER

# 配置文件路径
config_path = os.path.join(UPLOAD_FOLDER, "page/config.json")

# 剪贴板文件路径
CLIPBOARD_FILE = os.path.join(UPLOAD_FOLDER, "page/filev.json")


# 在线用户统计相关变量
online_users = {}
user_lock = threading.Lock()
SESSION_TIMEOUT = 300  # 5分钟无活动视为离线

def cleanup_expired_sessions():
    """清理过期的会话"""
    current_time = time.time()
    with user_lock:
        expired_users = []
        for user_id, last_active in online_users.items():
            if current_time - last_active > SESSION_TIMEOUT:
                expired_users.append(user_id)
        
        for user_id in expired_users:
            del online_users[user_id]
    
    # 每60秒清理一次
    threading.Timer(60, cleanup_expired_sessions).start()

# 启动清理定时器
cleanup_expired_sessions()

def update_user_activity(user_id):
    """更新用户活动时间"""
    with user_lock:
        online_users[user_id] = time.time()

def get_online_user_count():
    """获取当前在线用户数"""
    with user_lock:
        current_time = time.time()
        active_users = [user for user, last_active in online_users.items() 
                       if current_time - last_active <= SESSION_TIMEOUT]
        return len(active_users)

# 修改读取配置文件函数，确保能处理新的下载计数结构
def get_config():
    try:
        with open(config_path, 'r') as f:
            return json.load(f)
    except FileNotFoundError:
        return {}


# 保存配置文件
def save_config(config):
    with open(config_path, 'w') as f:
        json.dump(config, f, indent=4)


# 读取 IP 白名单
def get_whitelist():
    config = get_config()
    return config.get('whitelist', [])


# 检查 IP 是否在白名单中，支持 * 通配符
def is_ip_allowed(client_ip):
    whitelist = get_whitelist()
    for ip_pattern in whitelist:
        if ip_pattern.endswith('*'):
            if client_ip.startswith(ip_pattern[:-1]):
                return True
        elif client_ip == ip_pattern:
            return True
    return False


def client_ip_tool():
    # 尝试从 X-Forwarded-For 头中获取客户端真实 IP
    x_forwarded_for = request.headers.get('X-Forwarded-For')
    if x_forwarded_for:
        # X-Forwarded-For 头可能包含多个 IP 地址，第一个是客户端的真实 IP
        client_ip = x_forwarded_for.split(',')[0].strip()
    else:
        # 如果 X-Forwarded-For 头不存在，使用 request.remote_addr
        client_ip = request.remote_addr
    return client_ip


@app.before_request
def limit_ips():
    client_ip = client_ip_tool()
    if not is_ip_allowed(client_ip):
        return make_response(json.dumps({"code": 403, "msg": "IP not allowed", "ip": str(client_ip)}), 403)


@app.before_request
def manage_user_session():
    """管理用户会话"""
    if request.endpoint and not request.endpoint.startswith('static'):
        # 使用IP地址作为用户标识
        user_id = client_ip_tool()
        
        # 如果是访问主页或API，更新用户活动
        if request.endpoint in ['index', 'index2', 'get_online_users']:
            update_user_activity(user_id)

@app.route("/")
def index():
    # 指定静态文件目录
    return send_from_directory(app.static_folder, "index.html")

@app.route("/index2")
def index2():
    # 指定静态文件目录
    return send_from_directory(app.static_folder, "index2.html")

def mergeChunks(filename, folder, total_chunks):
    """
    合并文件分片
    :param filename: 文件名
    :param folder: 目标文件夹
    :param total_chunks: 分片总数
    """
    file_path = os.path.join(app.config['UPLOAD_FOLDER'], folder, filename)
    with open(file_path, 'wb') as outfile:
        for i in range(total_chunks):
            part_filename = f"{filename}.part{i}"
            part_file_path = os.path.join(app.config['UPLOAD_FOLDER'], folder, part_filename)
            with open(part_file_path, 'rb') as infile:
                outfile.write(infile.read())
            # 删除分片文件
            os.remove(part_file_path)

    # 更新下载计数
    config = get_config()
    download_counts = config.get('download_counts', {})
    download_counts[filename] = download_counts.get(filename, 0)
    config['download_counts'] = download_counts
    save_config(config)



# 记录每个文件已上传的分片
uploaded_chunks = {}

@app.route("/upload_file", methods=["POST"])
def upload_file():
     # 获取目标文件夹和文件名
    folder = request.form.get('folder', '')
    chunk_index = int(request.form.get('chunk_index', 0))
    total_chunks = int(request.form.get('total_chunks', 1))
    filename = request.form.get('filename')

    file = request.files['file']
    # 保存每个分片到单独的文件
    chunk_filename = f"{filename}.part{chunk_index}"
    chunk_file_path = os.path.join(app.config['UPLOAD_FOLDER'], folder, chunk_filename)
    file.save(chunk_file_path)

    # 记录已上传的分片
    if filename not in uploaded_chunks:
        uploaded_chunks[filename] = set()
    uploaded_chunks[filename].add(chunk_index)

    # 检查是否所有分片都已上传
    if len(uploaded_chunks[filename]) == total_chunks:
        # 合并文件分片
        mergeChunks(filename, folder, total_chunks)
        # 清除记录
        del uploaded_chunks[filename]

    return jsonify({"code": 0, "msg": "Upload successful"})

"""
合并文件分片
:param filename: 文件名
:param folder: 目标文件夹
:param total_chunks: 分片总数
"""
def mergeChunks(filename, folder, total_chunks):
    file_path = os.path.join(app.config['UPLOAD_FOLDER'], folder, filename)
    with open(file_path, 'wb') as outfile:
        for i in range(total_chunks):
            part_filename = f"{filename}.part{i}"
            part_file_path = os.path.join(app.config['UPLOAD_FOLDER'], folder, part_filename)
            with open(part_file_path, 'rb') as infile:
                outfile.write(infile.read())
            # 删除分片文件
            os.remove(part_file_path)


# 下载文件路由
@app.route("/download_file/<path:filename>")
def download_file(filename):
    file_path = os.path.join(app.config['UPLOAD_FOLDER'], filename)
    if os.path.exists(file_path):
        # 更新下载计数
        config = get_config()
        download_counts = config.get('download_counts', {})
        download_counts[filename] = download_counts.get(filename, 0) + 1
        config['download_counts'] = download_counts
        save_config(config)

                # 添加 Content-Disposition 头
        from flask import send_file
        return send_file(file_path, as_attachment=True, download_name=os.path.basename(file_path))
    else:
        return json.dumps({"code": 404, "msg": "File not found"})


@app.route("/delete_file", methods=["POST"])
def delete_file():
    filename = request.json.get('filename')
    file_path = os.path.join(app.config['UPLOAD_FOLDER'], filename)
    if os.path.exists(file_path):
        try:
            if os.path.isfile(file_path):
                os.remove(file_path)
            elif os.path.isdir(file_path):
                shutil.rmtree(file_path)
            # 更新配置文件
            config = get_config()
            file_stats = config.get('file_stats', {})
            if filename in file_stats:
                del file_stats[filename]
            save_config(config)
            return json.dumps({"code": 0, "msg": "Deletion successful"})
        except PermissionError:
            return json.dumps({"code": 500, "msg": "Permission denied, cannot delete file or folder"})
        except Exception as e:
            return json.dumps({"code": 500, "msg": f"An error occurred: {str(e)}"})
    else:
        return json.dumps({"code": 404, "msg": "File or folder not found"})


@app.route("/get_client_ip")
def get_client_ip_tool():
    client_ip = client_ip_tool()
    return json.dumps({"ip": client_ip})


# 静态文件路由
@app.route("/static/<path:path>")
def send_static(path):
    return send_from_directory(app.static_folder, path)

@app.route("/create_folder", methods=["POST"])
def create_folder():
    folderPath = request.json.get('folderPath')
    fullPath = os.path.join(app.config['UPLOAD_FOLDER'], folderPath)
    if not os.path.exists(fullPath):
        try:
            os.makedirs(fullPath)
            return json.dumps({"code": 0, "msg": "Folder created successfully"})
        except Exception as e:
            return json.dumps({"code": 1, "msg": str(e)})
    else:
        return json.dumps({"code": 1, "msg": "Folder already exists"})

@app.route("/get_file_list")
def get_file_list_route():
    result = get_file_list(app.config['UPLOAD_FOLDER'])
    return json.dumps(result)

@app.route("/preview_file/<path:filename>")
def preview_file(filename):
    file_path = os.path.join(app.config['UPLOAD_FOLDER'], filename)
    if os.path.exists(file_path):
        if filename.endswith(('.pdf', '.jpg', '.jpeg', '.png', '.gif', '.html', '.py', '.json')):
            return send_from_directory(os.path.dirname(file_path), os.path.basename(file_path))
        else:
            return json.dumps({"code": 400, "msg": "Preview not supported"})
    else:
        return json.dumps({"code": 404, "msg": "File not found"})


@app.route("/get_config_file")
def get_config_file():
    try:
        with open(config_path, 'r') as f:
            config = f.read()
        return json.dumps({"code": 0, "msg": "", "data": config})
    except FileNotFoundError:
        return json.dumps({"code": 404, "msg": "Config file not found"})


@app.route("/save_config_file", methods=["POST"])
def save_config_file_route():
    config = request.json.get('config')
    config_text = json.dumps(config, indent=4)
    result = save_config_file(config_text)
    return json.dumps(result)


@app.route("/get_servers")
def get_servers():
    # 从配置文件中获取服务器列表
    config = get_config()
    servers = config.get('servers', [])
    return json.dumps({"code": 0, "msg": "", "data": servers})

# 升级程序
@app.route("/upgrade_program", methods=["POST"])
def upgrade_program():
    return handle_upgrade_program(request, app)

@app.route("/upgrade_on_servers", methods=["POST"])
def upgrade_on_servers():
    data = request.json
    servers = data.get('servers', [])
    link = data.get('link')

    results = []
    for server in servers:
        try:
            ssh = paramiko.SSHClient()
            ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
            ssh.connect(server['host'], username=server['username'], password=server['password'])
            stdin, stdout, stderr = ssh.exec_command(f'wget {link}')
            exit_status = stdout.channel.recv_exit_status()
            if exit_status == 0:
                results.append({
                    'host': server['host'],
                    'status': 'success',
                    'message': 'Upgrade successful'
                })
            else:
                results.append({
                    'host': server['host'],
                    'status': 'failure',
                    'message': f'Upgrade failed: {stderr.read().decode()}'
                })
            ssh.close()
        except Exception as e:
            results.append({
                'host': server['host'],
                'status': 'failure',
                'message': f'Upgrade failed: {str(e)}'
            })

    return json.dumps({"code": 0, "msg": "", "data": results})

# 读取剪贴板列表
def build_folder_tree(root_path, prefix=''):
    """
    递归构建文件夹树结构
    :param root_path: 根目录路径
    :param prefix: 路径前缀
    :return: 文件夹树结构列表
    """
    result = []
    try:
        # 跳过不应该显示的文件夹
        skip_folders = ['page', '__pycache__', '.git']
        
        # 确保根目录存在
        if not os.path.exists(root_path):
            logging.warning(f"Root path does not exist: {root_path}")
            return result
        
        # 遍历根目录下的所有项目
        for item in os.listdir(root_path):
            # 跳过隐藏文件和指定的跳过文件夹
            if item.startswith('.') or item in skip_folders:
                continue
                
            item_path = os.path.join(root_path, item)
            # 只处理文件夹
            if os.path.isdir(item_path):
                # 构建相对路径，确保路径格式正确
                relative_path = os.path.normpath(os.path.join(prefix, item))
                # 创建文件夹节点
                folder_node = {
                    'name': item,
                    'path': relative_path,
                    'isLeaf': False,
                    'children': []
                }
                # 递归获取子文件夹
                subfolders = build_folder_tree(item_path, relative_path)
                if subfolders:
                    folder_node['children'] = subfolders
                else:
                    folder_node['isLeaf'] = True
                result.append(folder_node)
    except Exception as e:
        logging.error(f"Error building folder tree: {e}")
    return result

def get_clipboard_list():
    if os.path.exists(CLIPBOARD_FILE):
        with open(CLIPBOARD_FILE, 'r') as f:
            try:
                return json.load(f)
            except json.JSONDecodeError:
                return []
    return []
    
# 保存剪贴板内容
def save_clipboard_content(content):
    import datetime
    now = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
    clipboard_list = get_clipboard_list()
    new_item = {
        "content": content,
        "time": now
    }
    clipboard_list.append(new_item)
    # 检查剪贴板记录数量，超过五条则删除最早的一条
    if len(clipboard_list) > 50:
        clipboard_list.pop(0)
    with open(CLIPBOARD_FILE, 'w') as f:
        json.dump(clipboard_list, f, indent=4)
    return {"code": 0, "msg": "保存成功"}

# 获取剪贴板列表接口
@app.route("/get_clipboard_list")
def get_clipboard_list_route():
    clipboard_list = get_clipboard_list()
    # 按时间倒序排序
    sorted_clipboard_list = sorted(clipboard_list, key=lambda x: x['time'], reverse=True)

    return jsonify({"code": 0, "data": sorted_clipboard_list})


# 获取文件夹树接口
@app.route("/get_folder_tree")
def get_folder_tree():
    try:
        # 从上传根目录构建文件夹树
        root_path = app.config['UPLOAD_FOLDER']
        folder_tree = build_folder_tree(root_path)
        
        # 添加根目录节点
        root_node = {
            'name': '根目录',
            'path': '',
            'isLeaf': len(folder_tree) == 0,
            'children': folder_tree
        }
        
        return jsonify({"code": 0, "msg": "success", "data": [root_node]})
    except Exception as e:
        logging.error(f"Error getting folder tree: {e}")
        return jsonify({"code": 1, "msg": f"获取文件夹树失败: {str(e)}"})

# 移动文件接口
@app.route("/move_file", methods=["POST"])
def move_file():
    try:
        data = request.json
        source_path = data.get('source_path')
        target_path = data.get('target_path')
        
        if not source_path or not target_path:
            return jsonify({"code": 1, "msg": "源路径和目标路径不能为空"})
        
        # 构建完整路径，确保路径格式正确，避免重复的uploads前缀
        # 检查source_path是否已经包含uploads前缀
        if source_path.startswith('uploads/'):
            source_path = source_path[8:]  # 去掉uploads/前缀
        elif source_path.startswith('uploads'):
            source_path = source_path[7:]  # 去掉uploads前缀
            
        # 检查target_path是否已经包含uploads前缀
        if target_path.startswith('uploads/'):
            target_path = target_path[8:]  # 去掉uploads/前缀
        elif target_path.startswith('uploads'):
            target_path = target_path[7:]  # 去掉uploads前缀
            
        source_full_path = os.path.normpath(os.path.join(app.config['UPLOAD_FOLDER'], source_path))
        target_full_path = os.path.normpath(os.path.join(app.config['UPLOAD_FOLDER'], target_path))
        
        logging.debug(f"尝试移动文件: 源路径={source_path}, 完整源路径={source_full_path}")
        logging.debug(f"目标路径={target_path}, 完整目标路径={target_full_path}")
        
        # 检查源文件/文件夹是否存在
        if not os.path.exists(source_full_path):
            # 尝试检查是否有子文件夹路径问题
            possible_paths = []
            # 获取上传目录下的所有项目
            for item in os.listdir(app.config['UPLOAD_FOLDER']):
                item_path = os.path.join(app.config['UPLOAD_FOLDER'], item)
                # 检查是否包含源路径的某个部分
                if os.path.isdir(item_path) and source_path.startswith(item):
                    possible_paths.append(item_path)
            
            logging.debug(f"找不到源文件/文件夹，可能的路径: {possible_paths}")
            return jsonify({"code": 1, "msg": f"源文件或文件夹不存在: {source_full_path}"})
        
        # 检查目标文件夹是否存在
        if not os.path.exists(target_full_path):
            return jsonify({"code": 1, "msg": "目标文件夹不存在"})
        
        # 检查是否是移动到自身或子文件夹
        if os.path.commonpath([source_full_path, target_full_path]) == source_full_path:
            return jsonify({"code": 1, "msg": "不能移动到自身或其子文件夹"})
        
        # 获取源文件/文件夹名称
        source_name = os.path.basename(source_full_path)
        # 构建目标完整路径（包含文件名）
        new_path = os.path.join(target_full_path, source_name)
        
        # 检查目标位置是否已存在同名文件/文件夹
        if os.path.exists(new_path):
            return jsonify({"code": 1, "msg": "目标位置已存在同名文件或文件夹"})
        
        # 执行移动操作
        shutil.move(source_full_path, new_path)
        logging.debug(f"文件移动成功: 从 {source_full_path} 到 {new_path}")
        
        # 更新配置文件中的相关信息（如果需要）
        config = get_config()
        # 如果存在下载计数，更新路径
        if 'download_counts' in config and source_path in config['download_counts']:
            new_file_path = os.path.join(target_path, source_name)
            config['download_counts'][new_file_path] = config['download_counts'].pop(source_path)
            save_config(config)
        
        return jsonify({"code": 0, "msg": "移动成功"})
    except Exception as e:
        logging.error(f"Error moving file: {e}")
        return jsonify({"code": 1, "msg": f"移动失败: {str(e)}"})

# 保存剪贴板内容接口
@app.route("/save_clipboard_content", methods=["POST"])
def save_clipboard_content_route():
    content = request.json.get('content')
    if content:
        result = save_clipboard_content(content)
        return jsonify(result)
    return jsonify({"code": 1, "msg": "内容为空"})

@app.route("/api/online_users")
def get_online_users():
    """获取当前在线用户数"""
    count = get_online_user_count()
    return jsonify({
        "code": 0,
        "msg": "success",
        "data": {
            "online_users": count,
            "timestamp": datetime.now().isoformat()
        }
    })

if __name__ == "__main__":
    # 修改部分：指定 host 为 0.0.0.0，允许外部访问
    app.run(debug=True, host='0.0.0.0', port=35000)